An operating system performs various tasks relating to a computer system, including managing its hardware and software resources. Hardware resources include processors, primary storage (e.g., memory), secondary storage (e.g., hard disk or optical disk), printers, display adapters, network interface cards, input/output ports, etc. Software resources include application programs, user interfaces, device drivers, network protocol stacks, etc. The operating system manages and coordinates these resources to complete various tasks, such as under the direction of an application program.
Operating systems may provide functionality to application programs using subsystems. A subsystem is an operating system component that implements an operating system's application program interfaces (“APIs”). Subsystems may invoke functions of a kernel component. A kernel component is an operating system component that provides core operating system functions.
An operating system may have multiple subsystems, each exposing different operating system behaviors. As an example, an operating system may implement a portable operating system interface (“POSIX”) and a MICROSOFT WINDOWS (“WINDOWS”) interface as two separate subsystems. These subsystems may both function on a common operating system. Such an operating system may function both with application programs designed for POSIX and application programs designed for WINDOWS.
Software developers may use tools, such as compilers and debuggers, that are designed for use with one subsystem to design and test software written for another subsystem. As an example, software developers may use MICROSOFT VISUAL STUDIO, which is designed for use with the WINDOWS subsystem, to design and test application programs for POSIX.
A tool called a debugger is commonly employed by software developers to assist them in identifying various software defects. A debugger typically “attaches” to code to be debugged, after which it can be used by a human tester to monitor and control the execution of the debugged code. However, debuggers designed for use with a subsystem have many problems when used to debug application programs written for another subsystem. Three such problems include an inability to automatically attach to the application program being tested, an inability to attach to forked processes, and an inability to properly redirect exceptions. Each of these problems is discussed immediately below.
When a software developer desires to debug an application program, the software developer may start the debugger, identify the application program, and assume that the debugger will attach to the application program. When the debugger is designed for use with a native subsystem (e.g., WINDOWS) but the application program is designed for another subsystem (e.g., POSIX), the debugger attaches to a component that provides a terminal in the environment of the native subsystem. As an example, a POSIX.EXE component provides a terminal for the POSIX subsystem so that POSIX applications can receive input and provide output. When the debugger attempts to attach to a POSIX application program, it attaches to POSIX.EXE instead because, as far as the WINDOWS subsystem is concerned, the application being debugged is POSIX.EXE.
POSIX supports a concept known as forking. When an application program forks, the POSIX subsystem creates a new process that executes the application program. A software developer may desire to debug every forked process concurrently. However, because the debugger of a subsystem is unaware that the application program operating on another subsystem has forked, the debugger will not attach to the forked process.
Various software components may raise “exceptions” when they encounter abnormal conditions. Examples of abnormal conditions may include, e.g., running out of memory, failing to find a file, causing a floating point error of a processor, receiving an illegal instruction, referencing an un-allocated memory page, etc. The application program or other software components may have program logic to detect and handle exceptions. As an example, an application program may attempt to locate a file when a file cannot be found. As another example, a subsystem may indicate to a user to close application programs when memory cannot be allocated to an application program requiring additional memory. When an exception is raised, it may be provided to the debugger. However, an application program being debugged may have logic for handling the exception that the software developer may wish to test. Conventionally, a debugger that is designed to operate with a subsystem is unable to provide the exception to a software application program that is operating on another subsystem.
It would thus be highly desirable to provide a facility for debugging in an operating system with multiple subsystems.